#include "game.h"
#include "menu.h"
#include "LeaderBoard.h"
#include "blockOperation.h"
#include "drawBlock.h"
#include "DEBUG.h"

#include <iostream>
#include <cstdlib>
#include <stdlib.h>
#include <time.h>
#include <cassert>

using namespace std;

bool SaveScore (int score);

block_type field[height_matrix][width_matrix]; 
pivot block;

/**
 * @param KEYS: contain the key that can be pressed
 * @param keys: if true itDrawFile means that the key is still pressed
 * */

enum KEYS{DOWN, RIGHT, LEFT};            
static bool keys[3] = {false, false, false};    

/**
 * @param level start level of the game
 * @param total_row row cleared in a game
 * @param total_score score achieved in a game
 * @param next_block next block that will be created
 * */
int level = 1;
int total_row = 0;
int total_score = 0;
const int max_level = 15;
int max_score = 0;
block_type next_block;

void DrawDisplay(){

  /**
   * This function draw the background using the following Allegro function:
   * - al_draw_line
   * - al_draw_rounded_rectangle
   * - al_draw_text
   * - al_draw_textf
   */

  //costruzione campo - inizio
  al_draw_line(20, 50, 20, 650, al_map_rgb(255, 255, 255), 2);
  al_draw_line(320, 50, 320, 650, al_map_rgb(255, 255, 255), 2);
  al_draw_line(20, 650, 320, 650, al_map_rgb(255, 255, 255), 2);
  //costruzione campo - fine
   
  //costruzione next - inizio
  al_draw_rounded_rectangle(340, 50, 490, 160, 5, 5, al_map_rgb(255, 255, 255), 2);
  //costruzione next - fine
  
  //maximum_score
  al_draw_text(font18, al_map_rgb(255,255,255),340, 175, ALLEGRO_ALIGN_LEFT, "Maximum Score");
  //score
  al_draw_text(font18, al_map_rgb(255,255,255),340, 245, ALLEGRO_ALIGN_LEFT, "Score");
  //level
  al_draw_text(font18, al_map_rgb(255,255,255),340, 315, ALLEGRO_ALIGN_LEFT, "Level");
  //linee
  al_draw_text(font18, al_map_rgb(255,255,255),340, 385, ALLEGRO_ALIGN_LEFT, "Broken Line");
    
  //Tutorial
  al_draw_text(font18, al_map_rgb(200,200,200),340, 490, ALLEGRO_ALIGN_LEFT, "MOVE PIECE:");
  al_draw_text(font18, al_map_rgb(200,200,200),340, 510, ALLEGRO_ALIGN_LEFT, "arrows");
  al_draw_text(font18, al_map_rgb(200,200,200),340, 530, ALLEGRO_ALIGN_LEFT, "ROTATE PIECE:");
  al_draw_text(font18, al_map_rgb(200,200,200),340, 550, ALLEGRO_ALIGN_LEFT, "spacebar");
  al_draw_text(font18, al_map_rgb(200,200,200),340, 570, ALLEGRO_ALIGN_LEFT, "PAUSE GAME:");
  al_draw_text(font18, al_map_rgb(200,200,200),340, 590, ALLEGRO_ALIGN_LEFT, "escape");
    
 //costruzioni 4 campi destra - inizio
  al_draw_rounded_rectangle(340, 200, 490, 240, 5, 5, al_map_rgb(255, 0, 255), 2);
  al_draw_rounded_rectangle(340, 270, 490, 310, 5, 5, al_map_rgb(255, 0, 255), 2);
  al_draw_rounded_rectangle(340, 340, 490, 380, 5, 5, al_map_rgb(255, 0, 255), 2);
  al_draw_rounded_rectangle(340, 410, 490, 450, 5, 5, al_map_rgb(255, 0, 255), 2);
  //costruzioni 4 campi destra - fine
    
  //punteggi - inizio
    
  al_draw_textf(font18, al_map_rgb(255, 255, 255), 485, 210, ALLEGRO_ALIGN_RIGHT, "%d", max_score);
  al_draw_textf(font18, al_map_rgb(255, 255, 255), 485, 280, ALLEGRO_ALIGN_RIGHT, "%d", total_score);
  al_draw_textf(font18, al_map_rgb(255, 255, 255), 485, 350, ALLEGRO_ALIGN_RIGHT, "%d", level);
  al_draw_textf(font18, al_map_rgb(255, 255, 255), 485, 420, ALLEGRO_ALIGN_RIGHT, "%d", total_row);
    
  //punteggi - fine
}

void ResetGame(){
  total_score = 0;
  level = 1;
  total_row = 0;
  keys[DOWN] = false; 
  keys[RIGHT] = false; 
  keys[LEFT] = false;
}

void InitGame() {
  /**
   * This function initialize the game calling the reset function.
   * */
  ResetGame(); 
  DrawDisplay();
  ResetField();
}

void ResetField(){
  for(int H = 0; H < height_matrix; H++)
    for (int W = 0; W < width_matrix; W++)
      field[H][W] = N;
}

int Game(){  

  /**
   * In this function there is the game loop.
   */
     
  max_score = MaxScore();
  assert(max_score);
  InitGame();
  bool done_game = false; 
  bool redraw = false;
  short int move_down = 0;
  short int move_x = 0;
  srand(time(NULL));
    
  block.type = NewBlock();
  block = InitBlock(done_game);
  D1(cout<<"Block Type "<<block.type<<endl;)
        
  next_block = NewBlock();
  D2(cout<<"Next Block Type "<<next_block;)
    
  while (!done_game){
    al_wait_for_event(event_queue, &ev);
               
    if(ev.type == ALLEGRO_EVENT_KEY_DOWN){
      switch(ev.keyboard.keycode){
        case ALLEGRO_KEY_DOWN:
           keys[DOWN] = true;
           break;
        case ALLEGRO_KEY_RIGHT:
          keys[RIGHT] = true;
          break;
        case ALLEGRO_KEY_LEFT:
          keys[LEFT] = true;
          break;
        case ALLEGRO_KEY_SPACE:
          block.rotation++;
          D2(cout<<"Rotation Occurance: "<<block.rotation<<endl);
          Rotation();
          break;
        default:
          break;
      }
    }
    else if(ev.type == ALLEGRO_EVENT_KEY_UP){
      switch(ev.keyboard.keycode){
        case ALLEGRO_KEY_ESCAPE:        //**conferma uscita gioco?*/
          int button_exit;
          button_exit = al_show_native_message_box(
                       display,
                        "Warning",
                        "Quit game, are you sure?",
                        "If you quit you lose your actual game.\n"
                        "Score won't be saved.", NULL,
                      ALLEGRO_MESSAGEBOX_YES_NO);
          if (button_exit == 1){
            return Menu();
            total_score = 0;
          }
          break;
        case ALLEGRO_KEY_DOWN:
          keys[DOWN] = false;
          break;
        case ALLEGRO_KEY_RIGHT:
          keys[RIGHT] = false;
          break;
        case ALLEGRO_KEY_LEFT:
          keys[LEFT] = false;
          break;
        default:
          break;
      }
            
	  }
    else if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE){
      al_flush_event_queue(event_queue);
      total_score = 0;
      return Menu();
    }
    else if(ev.type == ALLEGRO_EVENT_TIMER){
      redraw = true;
            
      if (keys[RIGHT]){
        move_x += 10;
        if (move_x >= 100){
          block.x = MoveRight(block);
          move_x = 0;
          D2(cout<<"Block Info X: "<<block.x<<endl;)
          D2(cout<<"Block Info Y: "<<block.y<<endl;)
        }
      }
      else if (keys[LEFT]){
        move_x += 10;
        if (move_x >= 100){
          block.x = MoveLeft(block);
          move_x = 0;
          D2(cout<<"Block Info X: "<<block.x<<endl;)
          D2(cout<<"Block Info Y: "<<block.y<<endl;)
        }
      }
            
      if (keys[DOWN]){
        move_down += 15;
      }
    }
        
    if(redraw && al_is_event_queue_empty(event_queue)){
      redraw = false;
            
      move_down += level;
           
      DrawDisplay();
         
      if ((move_down >= 100)){
        if (MoveDown()){
          block.y += 1;
          move_down = 0;
          D2(cout<<"Block Info X: "<<block.x<<endl;)
          D2(cout<<"Block Info Y: "<<block.y<<endl;)
        }
        else{
          Block();
          DeleteRow();
          block.type = next_block;
          D2(cout<<"Delete: Block Type: "<<block.type<<endl);
          block = InitBlock(done_game);
          next_block = NewBlock();
          D2(cout<<"Delete: Next Block Type: " << next_block);
        }
      }
            
      DrawNextBlock();
      DrawField();
      DrawBlock();
            
	    al_flip_display();
	    al_clear_to_color(al_map_rgb(0,0,0));
      al_set_timer_count(timer, 1.0/60);
    }
  }
    
  return SaveScore (total_score);
}

pivot InitBlock(bool &done){ 
  block.x = 4;
  switch (block.type){
    case I:
    case O:
      block.y = 0;
      break;
    default:
      block.y = 1;
      break;
  }
    
  block.rotation =0;
    
  if (GameOver())
    done = true;
    
  return block;
}

block_type NewBlock(){
  int val = rand() % 100;
  D1(cout<<"Random Generator Value: "<<val<<endl;)
  if (val >= 0 && val < 10)
    return I;
  else if (val >= 10 && val < 25)
    return J;
  else if (val >= 25 && val < 40)
    return L;
  else if (val >= 40 && val < 55)
    return O;
  else if (val >= 55 && val < 70)
    return S;
  else if (val >= 70 && val < 85)
    return Z;
  else return T;    
}

bool GameOver(){
  switch (block.type){
    case I:
      if ((field[block.y][block.x - 1] != N) ||
          (field[block.y][block.x] != N) ||
          (field[block.y][block.x + 1] != N) ||
          (field[block.y][block.x + 2] != N))
        return true;
      break;
    case J:
      if ((field[block.y - 1][block.x - 1] != N) ||
          (field[block.y][block.x - 1] != N) ||
          (field[block.y][block.x] != N) ||
          (field[block.y][block.x + 1] != N))
        return true;
      break;
    case L:
      if ((field[block.y][block.x - 1] != N) ||
          (field[block.y][block.x] != N) ||
          (field[block.y][block.x + 1] != N) ||
          (field[block.y + 1][block.x + 1] != N))
        return true;
      break;
    case O:
      if ((field[block.y][block.x] != N) ||
          (field[block.y + 1][block.x] != N) ||
          (field[block.y][block.x + 1] != N) ||
          (field[block.y + 1][block.x + 1] != N))
        return true;
      break;
    case S:
      if ((field[block.y][block.x - 1] != N) ||
          (field[block.y][block.x] != N) ||
          (field[block.y - 1][block.x] != N) ||
          (field[block.y - 1][block.x - 1] != N))
        return true;
      break;
    case Z:
      if ((field[block.y - 1][block.x - 1] != N) ||
          (field[block.y - 1][block.x] != N) ||
          (field[block.y][block.x] != N) ||
          (field[block.y][block.x + 1] != N))
        return true;
      break;
    case T:
      if ((field[block.y][block.x - 1] != N) ||
          (field[block.y][block.x] != N) ||
          (field[block.y + 1][block.x + 1] != N) ||
          (field[block.y][block.x + 1] != N))
        return true;
      break;
    default:
      break;
	  //return false;
  }
  return false;
}

void DrawBlock(){
  D2(cout<<"Block Type: "<<block.type<<" | Block Rotation"<<block.rotation<<endl;)
  D2(cout<<"BlockPositionBefore X: "<<block.x<<" | BlockPositionBefore Y"<<block.y<<endl;)
  switch (block.type){
    case I:
      RotationI(block);
      break;
    case L:
      RotationL(block);
      break;
    case J:
      RotationJ(block);
      break;
    case O:
      RotationO(block);
      break;
    case S:
      RotationS(block);
      break;
    case Z:
      RotationZ(block);
      break;
    case T:
      RotationT(block);
      break;
    default:
      break;
  }
  D2(cout<<"BlockPositionAfter X: "<<block.x<<" | BlockPositionAfter Y"<<block.y<<endl;)
}

bool MoveDown(){
  D2(cout<<"Block Type: "<<block.type<<" | Block Rotation"<<block.rotation<<endl;)
  D2(cout<<"BlockPositionBefore X: "<<block.x<<" | BlockPositionBefore Y"<<block.y<<endl;)
  switch (block.type){
    case I:
      return MoveDownI(block);
    case J:
      return MoveDownJ(block);
    case L:
      return MoveDownL(block);
    case O:
      return MoveDownO(block);
    case S:
      return MoveDownS(block);
    case Z:
      return MoveDownZ(block);
    case T:
      return MoveDownT(block);
    default:
      return false;
      break;
  }
  D2(cout<<"BlockPositionAfter X: "<<block.x<<" | BlockPositionAfter Y"<<block.y<<endl;)
}

void Rotation(){
  if (!CanRotate()){
    block.rotation--;
  }         
}

bool CanRotate(){
  switch (block.type){
    case I:
      return CanMoveRotateI(block, false, 0);
    case J:
      return CanMoveRotateJ(block, false, 0);
    case L:
      return CanMoveRotateL(block, false, 0);
    case S:
      return CanMoveRotateS(block, false, 0);
    case Z:
      return CanMoveRotateZ(block, false, 0);
    case T:
      return CanMoveRotateT(block, false, 0);
    default:
      return false;
      break;
  }
}

void Block(){
  switch (block.type){
    case I:
      BlockI(block);
      break;
    case J:
      BlockJ(block);
      break;
    case L:
      BlockL(block);
      break;
    case O:
      BlockO(block);
      break;
    case S:
      BlockS(block);
      break;
    case Z:
      BlockZ(block);
      break;
    case T:
      BlockT(block);
      break;
    default:
      break;
  }
}

void DeleteRow(){
  bool row_complete;
  bool two_row = false;
  bool three_row = false;
  bool tetris = false;
  short int prev_row = -1;
  short int deleted_row = 0;
  short int score = 0;
  short int actual_row = 19;

  while (actual_row >= 0){
    row_complete = true;
    for (int W = 0; W < width_matrix; W++){
      if (field[actual_row][W] == N){
        row_complete = false;
        break;
      }
    }
        
    if (row_complete){
      if (three_row && (prev_row == actual_row))
        tetris = true;
      else if (two_row && (prev_row == actual_row))
        three_row = true;
      else if ((prev_row == actual_row) && (deleted_row == 1))
        two_row = true;
      ShiftRowDown(actual_row);
      deleted_row++;
      prev_row = actual_row;
    }
    
    else actual_row--;
  }
    
  if (deleted_row > 0){
    total_row += deleted_row;
    if (tetris)
      score = 800;
    else if (three_row)
      score = 400;
    else score = deleted_row * 100;

    total_score += score;
            
    if (level <= max_level)
      level = (total_row / 10) + 1;
  }
}

void ShiftRowDown(int row_deleted){
  for (int H = row_deleted; H >= 0; H--)
    for (int M = 0; M < width_matrix; M ++)
      if (H == 0)
        field[H][M] = N;
      else
        field[H][M] = field[H - 1][M];
}

void DrawField(){
  short int r;
  short int g;
  short int b;
  int H;
  int W;
  for(H = 0; H < height_matrix; H++)
    for (W = 0; W < width_matrix; W++){
      r = 0;
      g = 0;
      b = 0;
      switch (field[H][W]){
        case I:
          r = 0;
          g = 255;
          b = 255;
          break;
        case J:
          r = 0;
          g = 0;
          b = 255;
          break;
        case L:
          r = 255;
          g = 102;
          b = 0;
          break;
        case O:
          r = 255;
          g = 255;
          b = 0;
          break;
        case S:
          r = 102;
          g = 255;
          b = 0;
          break;
        case Z:
          r = 255;
          g = 0;
          b = 0;
          break;
        case T:
          r = 143;
          g = 0;
          b = 255;
          break;
        default:
          break;
      }
    DrawPivot(W, H, r, g, b);
  }
}

void DrawNextBlock(){
  const short int pivot_x = 13;
  const short int pivot_y = 2;
  switch (next_block){
    case I:
      DrawLeft(pivot_x - 1, pivot_y - 1, 0, 255, 255);
      DrawPivot(pivot_x - 1, pivot_y - 1, 0, 255, 255);
      DrawRight(pivot_x - 1, pivot_y - 1, 0, 255, 255);
      DrawRight(pivot_x, pivot_y - 1, 0, 255, 255);
      break;
    case J:
      DrawUpLeft(pivot_x, pivot_y, 0, 0, 255);
      DrawLeft(pivot_x, pivot_y, 0, 0, 255);
      DrawPivot(pivot_x, pivot_y, 0, 0, 255);
      DrawRight(pivot_x, pivot_y, 0, 0, 255);
      break;
    case L:
      DrawLeft(pivot_x, pivot_y, 255, 102, 0);
      DrawPivot(pivot_x, pivot_y, 255, 102, 0);
      DrawRight(pivot_x, pivot_y, 255, 102, 0);
      DrawUpRight(pivot_x, pivot_y, 255, 102, 0);
      break;
    case O:
      DrawPivot(pivot_x - 1, pivot_y - 1, 255, 255, 0);
      DrawDown(pivot_x - 1, pivot_y - 1, 255, 255, 0);
      DrawRight(pivot_x - 1, pivot_y - 1, 255, 255, 0);
      DrawDownRight(pivot_x - 1, pivot_y - 1, 255, 255, 0);
      break;
    case S:
      DrawLeft(pivot_x, pivot_y, 102, 255, 0);
      DrawPivot(pivot_x, pivot_y, 102, 255, 0);
      DrawUp(pivot_x, pivot_y, 102, 255, 0);
      DrawUpRight(pivot_x, pivot_y, 102, 255, 0);
      break;
    case Z:
      DrawUpLeft(pivot_x, pivot_y, 255, 0, 0);
      DrawUp(pivot_x, pivot_y, 255, 0, 0);
      DrawPivot(pivot_x, pivot_y, 255, 0, 0);
      DrawRight(pivot_x, pivot_y, 255, 0, 0);
      break;
    case T:
      DrawLeft(pivot_x, pivot_y, 143, 0, 255);
      DrawPivot(pivot_x, pivot_y, 143, 0, 255);
      DrawUp(pivot_x, pivot_y, 143, 0, 255);
      DrawRight(pivot_x, pivot_y, 143, 0, 255);
      break;
    default:
      break;
  }
}
